<template>
  <view class="v-uploader" :style="`--v-uploader-size: ${size}`">
    <v-uploader-item
      v-for="(image, imageIndex) in imageList"
      :key="imageIndex"
      :status="image.status"
      :url="image.url"
      :src="image.src"
      :image-index="imageIndex"
    />
    <v-uploader-insert v-if="imageList.length < maxLength" :limit="maxLength" />
    <!-- <view
      v-if="maxLength"
      class="counter"
    >
      {{ `${imageList.length}/${maxLength}` }}
    </view> -->
  </view>
</template>

<script setup lang="ts">
import type { UploaderStatus, UploaderItem } from './modules/types';
import VUploaderInsert from './modules/v-uploader-insert.vue';
import VUploaderItem from './modules/v-uploader-item.vue';

const props = withDefaults(
  defineProps<{
    modelValue?: string[];
    uploadUrl: string;
    uploadName: string;
    loading?: boolean;
    preList?: string[];
    size?: string;
    maxSize?: number;
    maxLength?: number;
    limitType?: string[];
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    extraData?: Record<string, any>;
  }>(),
  {
    modelValue: undefined,
    size: '180rpx',
    loading: false,
    preList: undefined,
    maxSize: 5 * 1024 * 1024,
    maxLength: 3,
    limitType: () => ['png', 'jpg', 'jpeg', 'image'],
    extraData: undefined
  }
);

interface Emits {
  'update:model-value': [val: string[]];
  'update:loading': [val: boolean];
  change: [list: UploaderItem[]];
}
const emits = defineEmits<Emits>();

const maxSize = toRef(props, 'maxSize');
const maxLength = toRef(props, 'maxLength');
const limitType = toRef(props, 'limitType');

const preList = toRef(props, 'preList');
const imageList = ref<UploaderItem[]>(
  preList.value?.map((url) => ({
    status: 'success',
    src: url,
    url
  })) || []
);

const chooseImage = async (count: number) => {
  const choosenFile = await new Promise<
    UniApp.ChooseImageSuccessCallbackResult | undefined
  >((resolve) =>
    uni.chooseImage({
      count,
      sourceType: ['album', 'camera'],
      extension: limitType.value,
      sizeType: ['compressed'],
      success: resolve,
      fail: () => resolve(undefined)
    })
  );

  const tempFiles = choosenFile?.tempFiles as
    | { path: string; size: number }[]
    | undefined;

  if (!tempFiles) return;
  for (let i = tempFiles.length - 1; i >= 0; i -= 1) {
    const { path, size } = tempFiles[i];
    const fileExt = path.replace(/.+\./, '').toLowerCase();
    if (!limitType.value.some((ext) => ext.toLowerCase() === fileExt)) {
      uni.showToast({ title: `不允许选择${fileExt}格式的文件`, icon: 'none' });
      return;
    }
    if (size > maxSize.value) {
      uni.showToast({ title: '超出上传大小', icon: 'none' });
      tempFiles.splice(i, 1);
    }
  }
  return tempFiles;
};

const insertImage = async () => {
  const insertList = await chooseImage(
    maxLength.value - imageList.value.length
  );
  if (!insertList?.length) return;
  imageList.value.push(
    ...insertList.map((image) => ({
      status: 'before-upload' as UploaderStatus,
      src: image.path,
      url: ''
    }))
  );
  updateNext();
};

const changeImage = async (imageIndex: number) => {
  const { status } = imageList.value[imageIndex];
  if (status === 'loading') {
    return uni.showToast({ title: '上传中，请稍后', icon: 'none' });
  }
  const insertList = await chooseImage(1);
  if (!insertList?.length) return;
  imageList.value.splice(
    imageIndex,
    1,
    ...insertList.map((image) => ({
      status: 'before-upload' as UploaderStatus,
      src: image.path,
      url: ''
    }))
  );
  updateNext();
};

const deleteImage = (imageIndex: number) => {
  imageList.value.splice(imageIndex, 1);
  emits(
    'update:model-value',
    imageList.value
      .filter((item) => item.status === 'success')
      .map((image) => image.url)
      .filter((item) => !!item)
  );
};

const userStore = $store.user();

const uploadUrl = toRef(props, 'uploadUrl');
const uploadName = toRef(props, 'uploadName');

const uploading = ref(false);
const startUploadingHandle = () => {
  uploading.value = true;
  emits('update:loading', true);
};
const endUploadingHandle = () => {
  uploading.value = false;
  emits('update:loading', false);
};
const updateNext = () => {
  if (uploading.value) return;
  const nextIndex = imageList.value.findIndex(
    (item) => item.status === 'before-upload'
  );
  if (nextIndex !== -1) {
    upload(nextIndex);
  } else {
    endUploadingHandle();
  }
};
const upload = async (imageIndex: number) => {
  const item = imageList.value[imageIndex];
  item.status = 'loading';
  startUploadingHandle();
  if (!userStore.getTokenAvailable()) {
    item.status = 'error';
    endUploadingHandle();
    return;
  }
  const token = await userStore.getToken();
  uni.uploadFile({
    url: $api._config.baseUrl + uploadUrl.value,
    filePath: item.src,
    fileType: 'image',
    name: uploadName.value,
    header: {
      // #ifdef MP-ALIPAY
      aliToken: token,
      // #endif
      // #ifdef MP-WEIXIN
      wxToken: token,
      // #endif
      appid: $api._config.appId
    },
    timeout: 15000,
    formData: props.extraData,
    success: (res) => {
      endUploadingHandle();
      const response = JSON.parse(res.data);
      if (response.code === 0 && response.data) {
        item.status = 'success';
        item.url = response.data;
      } else {
        item.status = 'error';
      }
      emits(
        'update:model-value',
        imageList.value
          .filter((item) => item.status === 'success')
          .map((image) => image.url)
          .filter((item) => !!item)
      );
      updateNext();
    },
    fail: (err) => {
      endUploadingHandle();
      console.log('fail:', err);
      console.log('uploader err', err);
      item.status = 'error';
      updateNext();
    }
  });
};

const setImageList = (list: string[]) => {
  imageList.value = list.map((image) => ({
    status: 'success',
    src: image,
    url: image
  }));
};

provide('insertImage', insertImage);
provide('changeImage', changeImage);
provide('deleteImage', deleteImage);

defineExpose({ setImageList });
</script>

<style lang="scss" scoped>
.v-uploader {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 8rpx;
  margin-top: 20rpx;
  .counter {
    position: absolute;
    right: 4rpx;
    bottom: 0;
    color: #999999;
    font-size: 20rpx;
  }
}
</style>
